/*
 *  eeemac Darwin Kernel Module for eeePC-related functions
 *
 *  Copyright (C) 2009 Christian Kohlschuetter
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Ths program is distributed in the hope that it will be useful,
 *  but WITOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTAILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Template Place, Suite 330, Boston, MA  02111-1307 USA
 *  
 *  ---------
 *
 *  This program contains code/inspirations taken from the following projects:
 *
 *  - eeepc-linux
 *  - eeetune
 *  - eeectl
 *  - xnu-speedstep
 * 
 *  WARNING:  This is an extremely *experimental* module!  This code has been
 *  developed through trial-and-error, which means that the authors don't really
 *  understand 100% what's going on here...  That means there's a chance that
 *  there could be unintended side-effects which might cause data loss or even
 *  physical damage!
 *
 *  Again, this code comes WITHOUT ANY WARRANTY whatsoever.
 */
#include <mach/mach_types.h>
#include <kern/locks.h>

#include <IOKit/IOLib.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/sysctl.h>


#define kextname "eeemac"
#define dbg(args...)    do { kprintf(kextname ": DEBUG " args); IOLog(kextname ": DEBUG " args); IOSleep(50); } while(0)
#define err(args...)    do { IOLog(kextname ": ERROR " args); IOSleep(50); } while(0)

#define info(args...)   do { kprintf(kextname ": " args); IOLog(kextname ": " args); } while(0)
#define chkpoint(arg)   kprintf(kextname " %s : %s\n", __FUNCTION__, arg)
#define fail(arg)  do { kprintf(kextname ": %s(%d) - fail '%s'\n", __FUNCTION__, __LINE__, arg); printf(kextname ": %s(%d) - fail '%s'\n", __FUNCTION__, __LINE__, arg); goto fail; } while(0)

#define SYNCHRONIZED(slock, code) lck_mtx_lock(slock);{ code };lck_mtx_unlock(slock);

#define EC_IDX_ADDRH 0x381
#define EC_IDX_ADDRL 0x382
#define EC_IDX_DATA 0x383
#define HIGH_BYTE(x) ((x & 0xff00) >> 8)
#define LOW_BYTE(x) (x & 0x00ff)

#define EC_ST00 0xF451          // Temperature of CPU (C)
#define EC_SC02 0xF463          // Fan PWM duty cycle (%)
#define EC_SC05 0xF466          // High byte of fan speed (RPM)
#define EC_SC06 0xF467          // Low byte of fan speed (RPM)
#define EC_SFB3 0xF4D3          // Flag byte containing SF25 (FANctrl)

typedef unsigned short i386_ioport_t;
inline unsigned char inb(i386_ioport_t port)
{
    unsigned char datum;
    asm volatile("inb %1, %0" : "=a" (datum) : "d" (port));
    return(datum);
}

inline void outb(unsigned char datum, i386_ioport_t port)
{
    asm volatile("outb %0, %1" : : "a" (datum), "d" (port));
}


static lck_grp_attr_t* eee_lck_grp_attr;
static lck_grp_t* eee_lck_grp;
static lck_attr_t* eee_lck_attr;
lck_mtx_t* ec_mutex;

unsigned char eee_ec_read(unsigned short address)
{
    unsigned char data;

	SYNCHRONIZED(ec_mutex, 
		outb(HIGH_BYTE(address), EC_IDX_ADDRH);
		outb(LOW_BYTE(address), EC_IDX_ADDRL);
	
		data = inb(EC_IDX_DATA);
    )
	
    return data;
}

bool eee_ec_write(unsigned short address, unsigned char data)
{

	SYNCHRONIZED(ec_mutex, 
	    outb(HIGH_BYTE(address), EC_IDX_ADDRH);
        outb(LOW_BYTE(address), EC_IDX_ADDRL);
        outb(data, EC_IDX_DATA);
    )
    return true;
}

static int eeekext_sysctl_fan_manual SYSCTL_HANDLER_ARGS
{
	int err = 0;
	int val;
	if (req->newptr) {
		err = SYSCTL_IN(req, &val, sizeof(int));
		if (err) return err;
		
		if(val != 0) {
			// SF25=1: Prevent the EC from controlling the fan.
			eee_ec_write(EC_SFB3, eee_ec_read(EC_SFB3) | 0x02);
		} else {
			// SF25=0: Allow the EC to control the fan.
			eee_ec_write(EC_SFB3, eee_ec_read(EC_SFB3) & ~0x02);
		}
	} else {
		// 1 if fan is in manual mode, 0 if controlled by the EC
		val = (eee_ec_read(EC_SFB3) & 0x02) ? 1 : 0;
		
		err = SYSCTL_OUT(req, &val, sizeof(int));
	}
	return err;
}
static int eeekext_sysctl_fan_speed SYSCTL_HANDLER_ARGS
{
	int err = 0;
	int val;
	if (req->newptr) {
		err = SYSCTL_IN(req, &val, sizeof(int));
		if (err) return err;
		
		if(val < 0) {
			val = 0;
		} else if(val > 100) {
			val = 100;
		}
		
		eee_ec_write(EC_SC02, val);
	} else {
		val = eee_ec_read(EC_SC02);
		
		err = SYSCTL_OUT(req, &val, sizeof(int));
	}
	return err;
}
static int eeekext_sysctl_fan_rpm SYSCTL_HANDLER_ARGS
{
	int err = 0;
	int val;
	if (req->newptr) {
		return EINVAL;
	} else {
		val = (eee_ec_read(EC_SC05) << 8) | eee_ec_read(EC_SC06);
		err = SYSCTL_OUT(req, &val, sizeof(int));
	}
	return err;
}
static int eeekext_sysctl_cpu_temperature SYSCTL_HANDLER_ARGS
{
	int err = 0;
	int val;
	if (req->newptr) {
		return EINVAL;
	} else {
		val = eee_ec_read(EC_ST00);
		err = SYSCTL_OUT(req, &val, sizeof(int));
	}
	return err;
}
SYSCTL_NODE(, OID_AUTO, eee, CTLFLAG_RW, 0, "eeemac options");
SYSCTL_NODE(_eee, OID_AUTO, fan, CTLFLAG_RW, 0, "Fan-related options");
SYSCTL_NODE(_eee, OID_AUTO, cpu, CTLFLAG_RW, 0, "CPU-related options");

SYSCTL_PROC  (_eee_fan, OID_AUTO, manual, CTLTYPE_INT | CTLFLAG_RW, 0, 0, &eeekext_sysctl_fan_manual, "I", "Override EC automatic fan control? (1=manual mode, 0=EC controlled)");
SYSCTL_PROC  (_eee_fan, OID_AUTO, speed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, &eeekext_sysctl_fan_speed, "I", "Fan speed (percent, 0-100)");
SYSCTL_PROC  (_eee_fan, OID_AUTO, rpm, CTLTYPE_INT | CTLFLAG_RD, 0, 0, &eeekext_sysctl_fan_rpm, "I", "Fan speed (RPM)");
SYSCTL_PROC  (_eee_cpu, OID_AUTO, temperature, CTLTYPE_INT | CTLFLAG_RD, 0, 0, &eeekext_sysctl_cpu_temperature, "I", "CPU Temperature (C)");

kern_return_t eeetest_start (kmod_info_t * ki, void * d) {
	eee_lck_grp_attr= lck_grp_attr_alloc_init();
	eee_lck_grp = lck_grp_alloc_init("eee", eee_lck_grp_attr);
	eee_lck_attr = lck_attr_alloc_init();
	
	ec_mutex = lck_mtx_alloc_init(eee_lck_grp, eee_lck_attr);
	
	sysctl_register_oid(&sysctl__eee); 	
	sysctl_register_oid(&sysctl__eee_cpu); 	
	sysctl_register_oid(&sysctl__eee_cpu_temperature); 	
	sysctl_register_oid(&sysctl__eee_fan); 	
	sysctl_register_oid(&sysctl__eee_fan_manual); 	
	sysctl_register_oid(&sysctl__eee_fan_speed); 	
	sysctl_register_oid(&sysctl__eee_fan_rpm); 	

    return KERN_SUCCESS;
}


kern_return_t eeetest_stop (kmod_info_t * ki, void * d) {
	eee_ec_write(EC_SFB3, eee_ec_read(EC_SFB3) & ~0x02);
	
	sysctl_unregister_oid(&sysctl__eee_fan_rpm);
	sysctl_unregister_oid(&sysctl__eee_fan_speed);
	sysctl_unregister_oid(&sysctl__eee_fan_manual);
	sysctl_unregister_oid(&sysctl__eee_fan);
	sysctl_unregister_oid(&sysctl__eee_cpu_temperature);
	sysctl_unregister_oid(&sysctl__eee_cpu);
	sysctl_unregister_oid(&sysctl__eee);
	
	lck_mtx_destroy(ec_mutex, eee_lck_grp);
	lck_mtx_free(ec_mutex, eee_lck_grp);
		
    return KERN_SUCCESS;
}

